package com.climber.android.commonres.widget;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.climber.android.commonres.R;
import com.climber.android.commonsdk.app.AppConstants;
import com.climber.android.commonsdk.arouter.ARouterPathConstants;

import io.ganguo.library.mvp.arouter.ARouterUtils;
import io.ganguo.library.mvp.arouter.BundleHelper;

/**
 * Created by mikes on 2017/12/5.
 */

public class ExpandableTextView extends LinearLayout implements View.OnClickListener {
    /* The default number of lines */
    private static final int MAX_COLLAPSED_LINES = 8;

    /* The default animation duration */
    private static final int DEFAULT_ANIM_DURATION = 300;

    /* The default alpha value when the animation starts */
    private static final float DEFAULT_ANIM_ALPHA_START = 0.7f;

    protected TextView mContentTv;

    protected TextView mActionButton; // Button to expand/collapse

    private boolean mRelayout;

    private boolean mCollapsed = true; // Show short version as default.

    private int mCollapsedHeight;

    private int mTextHeightWithMaxLines;

    private int mMaxCollapsedLines;

    private int mMarginBetweenTxtAndBottom;

    private int mAnimationDuration;

    private float mAnimAlphaStart;

    private boolean mAnimating;

    /* Listener for callback */
    private OnExpandStateChangeListener mListener;

    /* For saving collapsed status when used in ListView */
    private SparseBooleanArray mCollapsedStatus;
    private int mPosition;

    public ExpandableTextView(Context context) {
        this(context, null);
    }

    public ExpandableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public ExpandableTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    @Override
    public void setOrientation(int orientation){
        if(LinearLayout.HORIZONTAL == orientation){
            throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");
        }
        super.setOrientation(orientation);
    }

    @Override
    public void onClick(View view) {
        if (mActionButton.getVisibility() != View.VISIBLE) {
            return;
        }

        mCollapsed = !mCollapsed;
        changeActionButtonStyle();

        if (mCollapsedStatus != null) {
            mCollapsedStatus.put(mPosition, mCollapsed);
        }

        // mark that the animation is in progress
        mAnimating = true;

        Animation animation;
        if (mCollapsed) {
            animation = new ExpandCollapseAnimation(this, getHeight(), mCollapsedHeight);
            mContentTv.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mContentTv.scrollTo(0,0);
                }
            },mAnimationDuration+50);
        } else {
            animation = new ExpandCollapseAnimation(this, getHeight(), getHeight() +
                    mTextHeightWithMaxLines - mContentTv.getHeight());
        }

        animation.setFillAfter(true);
        animation.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                applyAlphaAnimation(mContentTv, mAnimAlphaStart);
            }
            @Override
            public void onAnimationEnd(Animation animation) {
                // clear animation here to avoid repeated applyTransformation() calls
                clearAnimation();
                // clear the animation flag
                mAnimating = false;

                // notify the listener
                if (mListener != null) {
                    mListener.onExpandStateChanged(mContentTv, !mCollapsed);
                }
            }
            @Override
            public void onAnimationRepeat(Animation animation) { }
        });

        clearAnimation();
        startAnimation(animation);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // while an animation is in progress, intercept all the touch events to children to
        // prevent extra clicks during the animation
        return mAnimating;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        findViews();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // If no change, measure and return
        if (!mRelayout || getVisibility() == View.GONE) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }
        mRelayout = false;

        // Setup with optimistic case
        // i.e. Everything fits. No button needed
        mActionButton.setVisibility(View.GONE);
        mContentTv.setMaxLines(Integer.MAX_VALUE);

        // Measure
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // If the text fits in collapsed mode, we are done.
        if (mContentTv.getLineCount() <= mMaxCollapsedLines) {
            return;
        }

        // Saves the text height w/ max lines
        mTextHeightWithMaxLines = getRealTextViewHeight(mContentTv);

        // Doesn't fit in collapsed mode. Collapse text view as needed. Show
        // button.
        if (mCollapsed) {
            mContentTv.setMaxLines(mMaxCollapsedLines);
        }
        mActionButton.setVisibility(View.VISIBLE);

        // Re-measure with new setup
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (mCollapsed) {
            // Gets the margin between the TextView's bottom and the ViewGroup's bottom
            mContentTv.post(new Runnable() {
                @Override
                public void run() {
                    mContentTv.scrollTo(0,0);
                    mMarginBetweenTxtAndBottom = getHeight() - mContentTv.getHeight();
                }
            });
            // Saves the collapsed height of this ViewGroup
            mCollapsedHeight = getMeasuredHeight();
        }
    }

    public void setOnExpandStateChangeListener(@Nullable OnExpandStateChangeListener listener) {
        mListener = listener;
    }

    private void setTextUrlSpan(){
        CharSequence text = mContentTv.getText();
        if(text.length() == 0){
            return;
        }
        if (text instanceof Spannable) {
            int end = text.length();
            Spannable spannable = (Spannable) mContentTv.getText();
            URLSpan[] urlSpans = spannable.getSpans(0,end, URLSpan.class);
            if(urlSpans.length > 0){
                for (URLSpan uri : urlSpans) {
                    String url = uri.getURL();
                    if (url.indexOf("http://") == 0 || url.indexOf("https://") == 0) {
                        InternalUrlSpan customUrlSpan = new InternalUrlSpan(url);
                        spannable.setSpan(customUrlSpan, spannable.getSpanStart(uri),
                                spannable.getSpanEnd(uri), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
                    }
                }
                mContentTv.setText(spannable);
            }
        }
    }

    public void setText(@Nullable CharSequence text) {
        mRelayout = true;
        mContentTv.setText(text);
        setTextUrlSpan();
        setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE);
    }

    public void setText(@Nullable CharSequence text, @NonNull SparseBooleanArray collapsedStatus, int position) {
        mCollapsedStatus = collapsedStatus;
        mPosition = position;
        boolean isCollapsed = collapsedStatus.get(position, true);
        clearAnimation();
        mCollapsed = isCollapsed;
        changeActionButtonStyle();
        setText(text);
        getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
        requestLayout();
    }

    @Nullable
    public CharSequence getText() {
        if (mContentTv == null) {
            return "";
        }
        return mContentTv.getText();
    }

    public TextView getContentTv(){
        return mContentTv;
    }

    private void init(AttributeSet attrs) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ExpandableTextViewAttr);
        mMaxCollapsedLines = typedArray.getInt(R.styleable.ExpandableTextViewAttr_maxCollapsedLines, MAX_COLLAPSED_LINES);
        mAnimationDuration = typedArray.getInt(R.styleable.ExpandableTextViewAttr_animDuration, DEFAULT_ANIM_DURATION);
        mAnimAlphaStart = typedArray.getFloat(R.styleable.ExpandableTextViewAttr_animAlphaStart, DEFAULT_ANIM_ALPHA_START);

        typedArray.recycle();

        // enforces vertical orientation
        setOrientation(LinearLayout.VERTICAL);

        // default visibility is gone
        setVisibility(GONE);
    }

    private void findViews() {
        mContentTv = (TextView) findViewById(R.id.expandable_text);
        mContentTv.setMovementMethod(new LinkTouchMovementMethod());

        mActionButton = (TextView) findViewById(R.id.expandable_action_button);
        changeActionButtonStyle();
        mActionButton.setOnClickListener(this);
    }

    private void changeActionButtonStyle() {
        mActionButton.setText(mCollapsed ? R.string.expand_and_read_more : R.string.collapse_content);
    }

    @SuppressLint("ObsoleteSdkInt")
    private static boolean isPostHoneycomb() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
    }

    private static boolean isPostLolipop() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private static void applyAlphaAnimation(View view, float alpha) {
        if (isPostHoneycomb()) {
            view.setAlpha(alpha);
        } else {
            AlphaAnimation alphaAnimation = new AlphaAnimation(alpha, alpha);
            // make it instant
            alphaAnimation.setDuration(0);
            alphaAnimation.setFillAfter(true);
            view.startAnimation(alphaAnimation);
        }
    }

    private static int getRealTextViewHeight(@NonNull TextView textView) {
        int textHeight = textView.getLayout().getLineTop(textView.getLineCount());
        int padding = textView.getCompoundPaddingTop() + textView.getCompoundPaddingBottom();
        return textHeight + padding;
    }

    class ExpandCollapseAnimation extends Animation {
        private final View mTargetView;
        private final int mStartHeight;
        private final int mEndHeight;

        public ExpandCollapseAnimation(View view, int startHeight, int endHeight) {
            mTargetView = view;
            mStartHeight = startHeight;
            mEndHeight = endHeight;
            setDuration(mAnimationDuration);
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            final int newHeight = (int)((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight);
            mContentTv.setMaxHeight(newHeight - mMarginBetweenTxtAndBottom);
            if (Float.compare(mAnimAlphaStart, 1.0f) != 0) {
                applyAlphaAnimation(mContentTv, mAnimAlphaStart + interpolatedTime * (1.0f - mAnimAlphaStart));
            }
            mTargetView.getLayoutParams().height = newHeight;
            mTargetView.requestLayout();
        }

        @Override
        public void initialize( int width, int height, int parentWidth, int parentHeight ) {
            super.initialize(width, height, parentWidth, parentHeight);
        }

        @Override
        public boolean willChangeBounds( ) {
            return true;
        }
    }

    public interface OnExpandStateChangeListener {
        /**
         * Called when the expand/collapse animation has been finished
         *
         * @param textView - TextView being expanded/collapsed
         * @param isExpanded - true if the TextView has been expanded
         */
        void onExpandStateChanged(TextView textView, boolean isExpanded);
    }


    /**
     * 实现自定义URL跳转
     */
    private class InternalUrlSpan extends ClickableSpan {
        private String url;

        public InternalUrlSpan(String url){
            this.url = url;
        }

        @Override
        public void onClick(View widget) {
            ARouterUtils.navigation(ARouterPathConstants.PATH_WEB_BROWSER,
                    ARouterUtils.INVALID_FLAG,
                    new BundleHelper()
                            .putString(AppConstants.PARAM_WEB_URL, url)
                    .build(),
                    R.anim.cbase__slide_in_from_right, R.anim.cbase__slide_out_to_left);
        }

        @Override
        public void updateDrawState(TextPaint ds) {
            ds.setColor(ds.linkColor);
            ds.setUnderlineText(true);
        }
    }

    public class LinkTouchMovementMethod extends LinkMovementMethod {

        @Override
        public void onTakeFocus(TextView widget, Spannable text, int dir) {
            Layout layout = widget.getLayout();

            if (layout != null && (dir & View.FOCUS_FORWARD) != 0) {
                widget.scrollTo(widget.getScrollX(),
                        layout.getLineTop(0));
            }
            if (layout != null && (dir & View.FOCUS_BACKWARD) != 0) {
                int padding = widget.getTotalPaddingTop() +
                        widget.getTotalPaddingBottom();
                int line = layout.getLineCount() - 1;

                widget.scrollTo(widget.getScrollX(),
                        layout.getLineTop(line+1) -
                                (widget.getHeight() - padding));
            }
        }

        @Override
        public boolean onTouchEvent(TextView textView, Spannable spannable, MotionEvent event) {
            checkUrlSpan(textView, spannable, event);
            return true;
        }

        private void checkUrlSpan(TextView textView, Spannable spannable, MotionEvent event){
            int action = event.getAction();
            int x = (int) event.getX();
            int y = (int) event.getY();

            x -= textView.getTotalPaddingLeft();
            y -= textView.getTotalPaddingTop();

            x += textView.getScrollX();
            y += textView.getScrollY();

            Layout layout = textView.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);

            ClickableSpan[] link = spannable.getSpans(off, off, ClickableSpan.class);
            if (link.length != 0) {
                for(ClickableSpan clickableSpan : link){
                    if (action == MotionEvent.ACTION_UP) {
                        clickableSpan.onClick(textView);
                        break;
                    } else if (action == MotionEvent.ACTION_DOWN) {
                        Selection.setSelection(spannable,
                                spannable.getSpanStart(clickableSpan),
                                spannable.getSpanEnd(clickableSpan));
                    }
                }
            } else {
                Selection.removeSelection(spannable);
            }
        }
    }
}